
// import * as _ from "../lib/underscore.js";
import * as _ from "underscore";
import {json as d3json, request as d3request} from "d3-request";
import {decodeEpak} from "../codec/decoder.js";
import {clamp} from "../util/math.js";
import {tr} from "../ux/translations.js";

/**
 * @param {string} resource
 * @param {Function} resolve
 * @param {Function} reject
 * @param {ProgressEvent|Error} err
 * @param {*} result
 * @returns {*}
 */
function xhrResolver(resource, resolve, reject, err, result) {
    if ((err ?? null) === null) {
        return resolve(result);
    }
    let error = err;
    if (err instanceof ProgressEvent) {
        const xhr = /** @type {XMLHttpRequest} */ err.target;
        const {status} = xhr;
        error = tr`Data download failed`;
        if (!navigator.onLine) {
            error = tr`Network not available`;
        } else if (status === 404) {
            error = tr`Data not available for this date`;
        } else if (status > 0) {
            error = `Error: HTTP ${status}: ${xhr.statusText}`;
        }
    }
    return reject({resource, error});
}

export function loadJson(resource) {
    return new Promise((resolve, reject) => {
        d3json(resource, (err, result) => xhrResolver(resource, resolve, reject, err, result));
    });
}

export const loadJsonOnce = _.memoize(loadJson);

function parseHeaders(s) {
    const result = Object.create(null);
    (s || "").split("\n").forEach(line => {
        const i = line.indexOf(":");
        if (i < 0) return;
        const key = line.substr(0, i).trim().toLowerCase();
        const value = line.substr(i + 1).trim();
        result[key] = (result[key] || []).concat(value);
    });
    return result;
}

function computeProgress(e) {
    let total = e.total;
    if (!e.lengthComputable) {
        const headers = parseHeaders(e.target.getAllResponseHeaders());
        total = (headers["x-amz-meta-uncompressed-size"] || [])[0];
    }
    return total ? clamp(e.loaded / total, 0, 1) : NaN;
}

export function loadEpak(resource) {
    return new Promise((resolve, reject) => {
        d3request(resource)
            .responseType("arraybuffer")
            .response(req => {
                if (req.response?.byteLength > 0) {
                    return decodeEpak(req.response);
                }
                throw new Error(tr`Data is empty`);
            })
            // .on("progress", /** @type {ProgressEvent} */ e => {
            //     const pct = computeProgress(e);
            //     if (pct) {
            //         console.log(resource, Math.round(pct * 100));
            //     } else {
            //         console.log(resource, e.loaded);
            //     }
            // })
            .get((err, res) => xhrResolver(resource, resolve, reject, err, res));
    });
}

function doLoad(path) {
    return /\.epak([/?#]|$)/.test(path) ? loadEpak(path) : loadJson(path);
}

function loadProduct(product, cancel) {
    return !product || cancel.requested ?
        Promise.resolve(product) :
        Promise.all(product.paths.map(doLoad)).then(files => {
            return cancel.requested ? product : Object.assign(product, product.builder.apply(product, files));
        });
}

export function loadProducts(products, cancel) {
    return Promise.all(products.map(product => loadProduct(product, cancel)));
}
